package middleware

import (
	"My_Blog02/utils/errmsg"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v4"
	"net/http"
	"os"
	"strings"
	"time"
)

// 获取密钥
var JwtKey = []byte(getSecretKey())

// 从系统环境变量中获取密钥
func getSecretKey() string {
	secret := os.Getenv("JWT_SECRET") //从系统环境变量JWT_SECRET读取密钥，避免硬编码
	if secret == "" {
		return "fsasfsdgfsdg"
	}
	return secret
}

// 自定义JWT的payload
type MyClaims struct {
	Account              string `json:"account"`
	jwt.RegisteredClaims        //可以方便地处理过期时间，发行者等标准字段
}

// 生成token
func SetToken(account string) (string, int) {
	expireTime := time.Now().Add(time.Hour * 24 * 30) //设置过期时间30天
	SetClaims := MyClaims{
		Account: account,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expireTime),
			Issuer:    "my blog02",
		},
	}
	//创建一个新的JWT令牌对象，指定签名算法为HS256	和自定义的payload（载荷）
	//该步骤会对头部和载荷进行Base64URL编码并拼接
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, SetClaims)
	//对令牌进行签名并生成最终的字符串形式
	//该方法会使用指定的HS256算法和密钥对前面拼接好的字符串（头部和载荷）进行签名计算
	//并且将签名与前面的字符串用.相连，生成最终Token
	tokenString, err := token.SignedString(JwtKey)
	if err != nil {
		return "", errmsg.ERROR
	}
	return tokenString, errmsg.SUCCSE
}

// 验证token
func CheckToken(token string) (*MyClaims, int) {
	//这个函数返回一个JWT令牌对象，包含了解析和验证token后的所有信息
	//解析jwt字符串，并同时验证签名和提取其中的Claims（载荷）
	//后面那个函数是回调函数，返回用于验证签名的密钥，必须与生成token时使用的密钥一致，否则验证失败
	//该方法内部会自动执行：
	//1.对token的头部和载荷进行Base64URL解码
	//2.验证签名（用回调函数返回的JwtKey和头部声明的算法重新计算签名并与token中的签名比对）
	//3.检查标准声明的有效性：如是否过期
	setToken, err := jwt.ParseWithClaims(token, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		return JwtKey, nil
	})
	if err != nil {
		return nil, errmsg.ERROR
	}
	//验证解析结果的有效性
	//setToken.Claims.(*MyClaims):将解析后的载荷转化为我们自定义的MyClaims类型（类型断言）
	//ok：判断类型转换是否成功（确保载荷格式符合预期）
	//setToken.Valid：判断token是否整体有效（经过前面的验证，确认签名正确且未过期等）
	if key, ok := setToken.Claims.(*MyClaims); ok && setToken.Valid {
		return key, errmsg.SUCCSE
	}
	return nil, errmsg.ERROR
}

// jwt中间件
func JwtToken() gin.HandlerFunc {
	return func(c *gin.Context) {
		//每次请求都重置一下错误码
		code := 0
		token := c.Request.Header.Get("Authorization")
		if token == "" {
			code = errmsg.ERROR
			c.JSON(http.StatusOK, gin.H{
				"code":    code,
				"message": errmsg.GetMsg(code),
			})
			c.Abort()
			return
		}
		//分割Bearer和Token
		//按空格分割字符串，最多分割成两部分
		checkToken := strings.SplitN(token, " ", 2)
		if len(checkToken) != 2 || checkToken[0] != "Bearer" {
			code = errmsg.ERROR
			c.JSON(http.StatusOK, gin.H{
				"code":    code,
				"message": errmsg.GetMsg(code),
			})
			c.Abort()
			return

		}
		//验证token
		key, tCode := CheckToken(checkToken[1])
		if tCode == errmsg.ERROR {
			code = errmsg.ERROR
			c.JSON(http.StatusOK, gin.H{
				"code":    code,
				"message": errmsg.GetMsg(code),
			})
			c.Abort()
			return
		}
		//检查token是否过期
		//其实验证token是否过期这一步骤在jwt.ParseWithClaims解析token时已经验证过了
		if key != nil && time.Now().Unix() > key.ExpiresAt.Unix() {
			code = errmsg.ERROR
			c.JSON(http.StatusOK, gin.H{
				"code":    code,
				"message": errmsg.GetMsg(code),
			})
			c.Abort()
			return
		}
		c.Set("key_account", key.Account)
		c.Next()
	}

}
